package xmla

import (
	"encoding/xml"
	"io"
	"strconv"
)

//成员
type Member struct {
	UniqueName  string
	Caption     string
	LevelName   string
	LevelNum    uint8
	DisplayInfo uint
}

//元组
type Tuple []Member

//轴
type Axis []Tuple

//数据立方体
type CubeData struct {
	ColumnAxis *Axis
	RowAxis    *Axis
	CellData   [][]interface{}
}

func NewCubeData(d *xml.Decoder) (cData *CubeData, err error) {
	cData = new(CubeData)
	if err := cData.readAxes(d); err != nil {
		return nil, err
	}
	if err := cData.readCellData(d); err != nil {
		return nil, err
	}
	return cData, nil
}

func (cd *CubeData) readAxes(d *xml.Decoder) error {
	i := -1
	memberBegin := false
	var isFinish bool = false
	var curAxis *Axis
	var curTuple *Tuple
	var curMember *Member
	var curMemAttr string = ""
lab:
	for !isFinish {
		t, err := d.Token()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		switch t := t.(type) {
		case xml.EndElement:
			if t.Name.Local == "Axis" {
				if i == 0 {
					cd.ColumnAxis = curAxis
				} else if i == 1 {
					cd.RowAxis = curAxis
					isFinish = true
				}
			} else if t.Name.Local == "Tuple" {
				*curAxis = append(*curAxis, *curTuple)
			} else if t.Name.Local == "Member" {
				*curTuple = append(*curTuple, *curMember)
				curMemAttr = ""
			}
		case xml.StartElement:
			if t.Name.Local == "Fault" {
				return NewErrSoapFault(d)
			} else if t.Name.Local == "Exception" {
				return NewErrExecuteResponse(d)
			} else if t.Name.Local == "Axis" {
				i++
				curAxis = new(Axis)
			} else if t.Name.Local == "Tuple" {
				curTuple = new(Tuple)
			} else if t.Name.Local == "Member" {
				curMember = new(Member)
				memberBegin = true
			} else {
				if memberBegin {
					curMemAttr = t.Name.Local
				}
			}
		case xml.CharData:
			text := string(t)
			switch curMemAttr {
			case "UName":
				curMember.UniqueName = text
			case "Caption":
				curMember.Caption = text
			case "LName":
				curMember.LevelName = text
			case "LNum":
				if s, err := strconv.Atoi(text); err == nil {
					curMember.LevelNum = uint8(s)
				}
			case "DisplayInfo":
				if s, err := strconv.Atoi(text); err == nil {
					curMember.DisplayInfo = uint(s)
				}
			}
		case xml.Comment:
			goto lab
		case xml.ProcInst:
			goto lab
		}
	}
	return nil
}

type cellVal struct {
	Ordinal  uint
	dataType string
	value    string
}

func (cd *CubeData) readCellData(d *xml.Decoder) error {
	valBegin := false
	var isFinish bool = false
	var pcVal *cellVal
lab:
	for !isFinish {
		t, err := d.Token()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		switch t := t.(type) {
		case xml.EndElement:
			if t.Name.Local == "CellData" {
				isFinish = true
			} else if t.Name.Local == "Cell" {
				cd.setCellData(pcVal)
			} else if t.Name.Local == "Value" {
				valBegin = false
			}
		case xml.StartElement:
			if t.Name.Local == "CellData" {
				rowNum := len(*cd.RowAxis)
				colNum := len(*cd.ColumnAxis)
				cd.CellData = make([][]interface{}, rowNum, rowNum)
				for i := 0; i < rowNum; i++ {
					cd.CellData[i] = make([]interface{}, colNum, colNum)
				}
			} else if t.Name.Local == "Cell" {
				pcVal = new(cellVal)
				for _, attr := range t.Attr {
					if attr.Name.Local == "CellOrdinal" {
						if s, err := strconv.Atoi(attr.Value); err == nil {
							pcVal.Ordinal = uint(s)
						}
						break
					}
				}
			} else if t.Name.Local == "Value" {
				valBegin = true
				for _, attr := range t.Attr {
					if attr.Name.Local == "type" {
						pcVal.dataType = attr.Value
						break
					}
				}
			}
		case xml.CharData:
			if valBegin {
				pcVal.value = string(t)
			}
		case xml.Comment:
			goto lab
		case xml.ProcInst:
			goto lab
		}
	}
	return nil
}
func (cd *CubeData) setCellData(cv *cellVal) error {
	var orainal int = int(cv.Ordinal)
	var colNum int = len(*cd.ColumnAxis)
	i := orainal / colNum
	j := orainal % colNum
	var thisVal interface{}
	switch cv.dataType {
	case "xsd:double":
		if val, err := strconv.ParseFloat(cv.value, 64); err == nil {
			thisVal = val
		} else {
			return err
		}
	case "xsd:float":
		if val, err := strconv.ParseFloat(cv.value, 32); err == nil {
			thisVal = val
		} else {
			return err
		}
	case "xsd:long":
		if val, err := strconv.ParseInt(cv.value, 0, 64); err == nil {
			thisVal = val
		} else {
			return err
		}
	case "xsd:int":
		if val, err := strconv.ParseInt(cv.value, 0, 32); err == nil {
			thisVal = val
		} else {
			return err
		}
	default:
		thisVal = cv.value
	}
	cd.CellData[i][j] = thisVal
	return nil
}
